//
//  NetworkController.m
//  TabulaImaginarius
//
//  Created by TA2 on 3/25/11.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import "NetworkController.h"
#include <netinet/tcp.h>
//#include <netinet/in.h>

@implementation NetworkController

@synthesize bonjourIsActive;
@synthesize networkProtocols;
@synthesize hasFoundHost;

static NetworkController *networkController = nil;

- (id)init {
	[super init];
	bonjourIsActive = YES;
	networkProtocols = [[NSMutableDictionary alloc] init];
    clientSocket = nil;
    hostingSocket = nil;
	return self;
}

- (void)dealloc {
    [self leaveSession];
	[bonjourService release];
    bonjourService = nil;
	[bonjourBrowser release];
    bonjourBrowser = nil;
    [connectedSockets release];
    connectedSockets = nil;
    [networkProtocols release];
    networkProtocols = nil;
    [currentConnectIp release];
    currentConnectIp = nil;
	[super dealloc];
}

- (void)startBonjourService {
	if (bonjourService) {
		[bonjourService stop];
		[bonjourService release];
	}
	if (((Player *)[Players sharedLocalPlayer]).isHost) {
		bonjourService = [[NSNetService alloc] initWithDomain:@"local."
														 type:@"_rpg._tcp."
														 name:[((Player *)[Players sharedLocalPlayer]).alias stringByAppendingString:@"_host"] port:((Player *)[Players sharedLocalPlayer]).port];
	} else {
		bonjourService = [[NSNetService alloc] initWithDomain:@"local."
														 type:@"_rpg._tcp."
														 name:[((Player *)[Players sharedLocalPlayer]).alias stringByAppendingString:@"_client"] port:((Player *)[Players sharedLocalPlayer]).port];
	}
	if (bonjourService) {
		[bonjourService setDelegate:self];
		[bonjourService publish];
	}
}

- (void)startBonjourBrowser {
	if (bonjourBrowser) {
        [[Players sharedBonjourPlayers] removeAllObjects];
		[bonjourBrowser stop];
		[bonjourBrowser release];
	}
	bonjourBrowser = [[NSNetServiceBrowser alloc] init];
	if (bonjourBrowser) {
		[bonjourBrowser retain];
		[bonjourBrowser setDelegate:self];
		[bonjourBrowser searchForServicesOfType:@"_rpg._tcp." inDomain:@"local."];
	}
}

//put found services in list
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser
		   didFindService:(NSNetService *)service
			   moreComing:(BOOL)more {
	[service retain];
	[service setDelegate:self];
	[service resolveWithTimeout:-1];
}

- (void)netServiceDidResolveAddress:(NSNetService *)service {
	if (service.addresses.count > 0) {
		struct sockaddr_in  *socketAddress = nil;
		socketAddress = (struct sockaddr_in *)[[service.addresses objectAtIndex:0] bytes];	
		Player *newPlayer = [[Player alloc] init];
		NSArray* aliasArray = [[NSArray alloc]initWithArray:[service.name componentsSeparatedByString:@"_"]];
		newPlayer.alias = [service.name substringToIndex:service.name.length - ((NSString *)[aliasArray objectAtIndex:aliasArray.count - 1]).length - 1];
		newPlayer.ip = [NSString stringWithFormat: @"%s",
						inet_ntoa(socketAddress->sin_addr)];
		newPlayer.port = service.port;
		if ([((NSString *)[aliasArray objectAtIndex: aliasArray.count - 1]) isEqualToString: @"client"]) {
			newPlayer.isHost = NO;
		} else {
			newPlayer.isHost = YES;
		}
		if (![[Players sharedLocalPlayer].alias isEqualToString:newPlayer.alias]) {
			[[Players sharedBonjourPlayers] addObject:newPlayer];
		} else {
			[Players sharedLocalPlayer].ip = newPlayer.ip;
		}
        [newPlayer release];
        [aliasArray release];
	}
}

- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser 
		 didRemoveService:(NSNetService *)netService 
			   moreComing:(BOOL)moreServicesComing {
	NSArray *aliasArray = [[NSArray alloc]initWithArray:[netService.name componentsSeparatedByString:@"_"]];
	NSString *removeAlias = [netService.name substringToIndex:netService.name.length - ((NSString *)[aliasArray objectAtIndex:aliasArray.count - 1]).length - 1];
	for (int i = 0; i < [Players sharedBonjourPlayers].count; i++) {
		if ([((Player *)[[Players sharedBonjourPlayers] objectAtIndex:i]).alias isEqualToString:removeAlias]) {
			[[Players sharedBonjourPlayers] removeObjectAtIndex: i];
		}
	}
    [aliasArray release];
}

- (BOOL)startSession {
	((Player *)[Players sharedLocalPlayer]).isHost = YES;
	hostingSocket = [[AsyncSocket alloc] init];
	if (connectedSockets) {
		[connectedSockets release];
	}
	connectedSockets = [[NSMutableArray alloc] init];
	[hostingSocket setDelegate:self];
	[hostingSocket acceptOnPort:((Player *)[Players sharedLocalPlayer]).port error:nil];
    
    keepNetworkAlive = YES;
    dispatch_queue_t keepAlive = dispatch_queue_create("keepAlive", NULL);
	
	dispatch_async(keepAlive, ^{
		while (keepNetworkAlive) {
			usleep(1000000);
            [self performSelectorOnMainThread:@selector(keepAlive) withObject:nil waitUntilDone:NO];
		}
	});
	return YES;
}

- (void)keepAlive {
    [self sendDataToAll:[@"|≤|" dataUsingEncoding: NSUTF8StringEncoding]  withTag:0];
}

- (void)joinSession:(NSString *)ip port:(UInt16)port {	
	clientSocket = [[AsyncSocket alloc] init];
	[clientSocket setDelegate:self];
	[clientSocket connectToHost:ip onPort:port withTimeout:1500000 error:nil];
	currentConnectIp = [ip retain];
	currentConnectPort = port;
}

- (void)leaveSession {
    keepNetworkAlive = NO;
    if (clientSocket) {
        [clientSocket setDelegate:nil];
        [clientSocket disconnect];
        [clientSocket release];
        clientSocket = nil;
    }
    if (hostingSocket) {
        ((Player *)[Players sharedLocalPlayer]).isHost = NO;
        bonjourIsActive = NO;
        [[NetworkController sharedNetworkController] startBonjourService];
        [hostingSocket setDelegate:nil];
        [hostingSocket disconnect];
        [hostingSocket release];
        hostingSocket = nil;
        for (AsyncSocket *socket in connectedSockets) {
            [socket setDelegate:nil];
            [socket disconnect];
        }
        [connectedSockets removeAllObjects];
    }
    while (YES) {
        if (!isReading) {
            [networkProtocols removeAllObjects];
            break;
        }
        usleep(100);
    }
}

- (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket {
	[connectedSockets addObject:newSocket];
	[sock readDataWithTimeout:-1 tag:1];
}

- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
    //disable Nagle algorithm:
    CFSocketRef cfsock = [sock getCFSocket]; 
    CFSocketNativeHandle rawsock = CFSocketGetNative(cfsock); 
    int flag = 1; 
    int result = setsockopt(rawsock, IPPROTO_TCP, TCP_NODELAY,(char *)&flag, sizeof(int)); 
    if (result != 0) {
        NSLog(@"Could Not Disable Nagle...");
    } else {
        NSLog(@"Nagle Is Disabled.");
    }
    
	hasFoundHost = YES;
	[sock readDataWithTimeout:-1 tag:1];
	if (isReconnecting) {
		[[[NetworkController sharedNetworkController].networkProtocols objectForKey:[NSString stringWithString:@"PP"]] setupProtocol];
		[[[NetworkController sharedNetworkController].networkProtocols objectForKey:[NSString stringWithString:@"BGP"]] setupProtocol];
		[[[NetworkController sharedNetworkController].networkProtocols objectForKey:[NSString stringWithString:@"TP"]] setupProtocol];
        [[[NetworkController sharedNetworkController].networkProtocols objectForKey:[NSString stringWithString:@"OP"]] setupProtocol];
		[[[NetworkController sharedNetworkController].networkProtocols objectForKey:[NSString stringWithString:@"DP"]] setupProtocol];
		isReconnecting = NO;
	}
}

- (BOOL)onSocketWillConnect:(AsyncSocket *)sock {
	return YES;
}

- (void)onSocketDidDisconnect:(AsyncSocket *)sock {
	if ([Players sharedLocalPlayer].isHost) {
		if (sock == hostingSocket) {
			[hostingSocket acceptOnPort:((Player *)[Players sharedLocalPlayer]).port error:nil];
		} else {
			[connectedSockets removeObject:sock];
		}
	} else {
		if (sock == clientSocket) {
			isReconnecting = YES;
			[clientSocket connectToHost:currentConnectIp onPort:currentConnectPort withTimeout:1500000 error:nil];
		}
	}	
}

- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err {
}

- (void)onSocketDidSecure:(AsyncSocket *)sock {
}

- (NSTimeInterval)onSocket:(AsyncSocket *)sock
  shouldTimeoutReadWithTag:(long)tag
                   elapsed:(NSTimeInterval)elapsed
                 bytesDone:(NSUInteger)length {
	return 0;
}

- (void)sendData:(NSData *)packet toSocket:(AsyncSocket *)socket withTag:(int)tag {
	[socket writeData:packet withTimeout:-1 tag:tag];
}

- (void)sendDataToAll:(NSData *)packet withTag:(int)tag {
	if ([Players sharedLocalPlayer].isHost)  {
		for (AsyncSocket *socket in connectedSockets) {
			[socket writeData:packet withTimeout:-1 tag:tag]; 
		}
	} else {
		[clientSocket writeData:packet withTimeout:-1 tag:tag];
	}
}

- (void)sendDataToAll:(NSData *)packet withTag:(int)tag butSocket:(AsyncSocket *)ignoredSocket {
	if ([Players sharedLocalPlayer].isHost)  {
		for (AsyncSocket *socket in connectedSockets) {
			if (socket != ignoredSocket) {
				[socket writeData:packet withTimeout:-1 tag:tag]; 
			}
		}
	} else {
		if (clientSocket != ignoredSocket) {
			[clientSocket writeData:packet withTimeout:-1 tag:tag];
		}
	}
}

- (void)onSocket:(AsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)length tag:(long)tag {
	[sock readDataWithTimeout:-1 tag:tag];
}

- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    if ([networkProtocols count] > 0) {
        isReading = YES;
        NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSArray *argumentsTemp = [dataString componentsSeparatedByCharactersInSet:
                                  [NSCharacterSet characterSetWithCharactersInString:@"|"]];
        NSMutableArray *arguments = [NSMutableArray arrayWithArray:argumentsTemp];
        while([arguments containsObject:@"≤"]) {
            if ([arguments indexOfObject:@"≤"] == [arguments count] - 1) {
                [(NetworkProtocol *)[networkProtocols objectForKey:[NSString stringWithString:[arguments objectAtIndex:0]]] readPacket:arguments fromSocket:sock];
                break;
            } else {
                NSRange range;
                range.location = 0;
                range.length = [arguments indexOfObject:@"≤"] + 1;
                NSArray *tempArray = [arguments subarrayWithRange:range];
                [arguments removeObjectsInRange:range];
                [(NetworkProtocol *)[networkProtocols objectForKey:[NSString stringWithString:[tempArray objectAtIndex:0]]] readPacket:tempArray fromSocket:sock];
            }
        }
        [sock readDataWithTimeout:-1 tag:tag];
        [dataString release];
        isReading = NO;
    }
}

+ (NSData *)argumentArrayToData:(NSArray *)arguments {
	NSString *dataString = [NSString stringWithString:@""];
	for (NSString *argument in arguments) {
		dataString = [[dataString stringByAppendingString:(NSString *)argument] stringByAppendingString:@"|"];
	}
	NSData *data = [dataString dataUsingEncoding: NSUTF8StringEncoding];
	return data;
}

- (void)netServiceDidStop:(NSNetService *)sender {
}

- (void)netServiceDidPublish:(NSNetService *)sender {
}

+ (NetworkController*)sharedNetworkController {
    if (networkController == nil) {
        networkController = [[super allocWithZone:NULL] init];
    }
    return networkController;
}

+ (id)allocWithZone:(NSZone *)zone {
    return [[self sharedNetworkController] retain];
}

- (id)copyWithZone:(NSZone *)zone {
    return self;
}

- (id)retain {
    return self;
}

- (NSUInteger)retainCount {
    return NSUIntegerMax;  //denotes an object that cannot be released
}

- (void)release {
    //do nothing
}

- (id)autorelease {
    return self;
}

@end
